package br.ufrj.dcc.tp.servidor.model;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import br.ufrj.dcc.tp.servidor.interfaces.INotifier;
import br.ufrj.dcc.tp.servidor.log.Log;
import br.ufrj.dcc.tp.servidor.log.LogLevel;
import br.ufrj.dcc.tp.servidor.model.exception.ServerException;
import br.ufrj.dcc.tp.utils.Config;
import br.ufrj.dcc.tp.utils.interfaces.ISocketMessage;

/***
 * SocketWorker faz o handshake com o cliente.
 * Verifica se eh um cliente novo ou um cliente antigo
 * Se for um cliente novo ele registra o cliente.
 * Se for um cliente antigo ele atribui a conexao como uma conexao de saida.
 * @author wakim
 */
public class SocketWorker extends Thread {
	
	private Socket socket;
	private INotifier notifier;
	private ObjectInputStream oi;
	private ObjectOutputStream oo;
	
	public SocketWorker(Socket socket, INotifier notifier) {
		this.socket = socket;
		this.notifier = notifier;
	}
	
	@Override
	public void run() {
		ISocketMessage message = null;
		
		try {
			oo = new ObjectOutputStream(socket.getOutputStream());
			oi = new ObjectInputStream(socket.getInputStream());
			socket.setSoTimeout(Config.IO_READ_TIMEOUT.toInteger());
		} catch( IOException ioe ) {
			Log.write(this.getClass(), ioe);
			processCloseAction();
			return;
		}
		
		try {
			while(! isInterrupted() ) {
				try {
				
					message = (ISocketMessage) oi.readObject();
					
					Log.write(this.getClass(), LogLevel.DEBUG, message.getAction().toString() + " Chamada por " + message.getSender() + ".");
					
					switch(message.getAction()) {
						case CREATE:
							processCreateAction(message);
							return;
						case CLOSE:
							processCloseAction();
							return;
					}
				} catch( InterruptedIOException e ) {}
				
				try {
					Thread.sleep(Config.SLEEP_TIMEOUT.toInteger());
				} catch( InterruptedException e ) {
					break;
				}
			}
		} catch( Exception e ) {
			Log.write(this.getClass(), LogLevel.SEVERE, "Erro ao processar requisicao de cliente.");
			Log.write(this.getClass(), e);
			processCloseAction();
		}
	}
	
	private void processCreateAction(ISocketMessage message) throws ServerException, IOException {
		
		if( message.getSender() == null ) {
			throw new ServerException("Nome nao enviado.");
		}
		
		DefaultClientImpl client = null;
		
		try {
			oo.writeObject(message);
			oo.flush();
			
			client = new DefaultClientImpl( message.getSender(), socket, oo, oi, notifier );
			notifier.subscribe(client);
			client.start();
			
			notifier.notifySubscriptionsChange(client, "Conectou");
		} catch( IOException e ) {
			Log.write(this.getClass(), e);
			if( client != null ) {
				notifier.unsubscribe(client, true);
			}
		}
	}
	
	private void processCloseAction() {
		try {
			oi.close();
			oo.close();
			socket.close();
		} catch(Exception e) {
			Log.write(this.getClass(), LogLevel.SEVERE, "Erro ao finalizar conexao do cliente.");
			Log.write(this.getClass(), e);
		}
	}
}
